/*******************************************************************************
 * Copyright (c) 2004-2009, Jean-Marc François. All Rights Reserved.
 * Originally licensed under the New BSD license.  See the LICENSE_OLD file.
 * Copyright (c) 2013, Timo Klerx. All Rights Reserved.
 * Now licensed under LGPL. See the LICENSE file.
 * This file is part of jhmmt.
 * 
 * jhmmt is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * jhmmt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with jhmmt.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package be.ac.ulg.montefiore.run.jahmm.toolbox;

import java.util.ArrayList;
import java.util.Random;

import be.ac.ulg.montefiore.run.jahmm.Hmm;
import be.ac.ulg.montefiore.run.jahmm.Observation;

/**
 * Generates sequences of markovian observations given a HMM.
 */
public class MarkovGenerator<O extends Observation> {
	private final Hmm<O> hmm;
	private int stateNb;
	private final Random r;

	private static Random masterSeed = new Random(12343464565671234l);

	/**
	 * Initializes a Markovian generator.
	 * 
	 * @param hmm
	 *            An Hidden Markov Model that perfectly models the sequences
	 *            generated by this object.
	 */
	public MarkovGenerator(Hmm<O> hmm) {
		r = new Random(masterSeed.nextLong());
		if (hmm == null)
			throw new IllegalArgumentException("Invalid null HMM");

		this.hmm = hmm;
		newSequence();
	}

	/**
	 * Initializes a Markovian generator.
	 * 
	 * @param hmm
	 *            An Hidden Markov Model that perfectly models the sequences
	 *            generated by this object.
	 */
	public MarkovGenerator(Hmm<O> hmm, Random r) {
		this.r = r;
		if (hmm == null)
			throw new IllegalArgumentException("Invalid null HMM");

		this.hmm = hmm;
		newSequence();
	}

	/**
	 * Generates a new (pseudo) random observation.
	 * 
	 * @return The generated observation.
	 */
	public O observation() {
		O o = hmm.getOpdf(stateNb).generate();
		double rand = r.nextDouble();

		for (int j = 0; j < hmm.nbStates() - 1; j++)
			if ((rand -= hmm.getAij(stateNb, j)) < 0) {
				stateNb = j;
				return o;
			}

		stateNb = hmm.nbStates() - 1;
		return o;
	}

	/**
	 * Generates a new (pseudo) random observation sequence and start a new one.
	 * 
	 * @param length
	 *            The length of the sequence.
	 * @return An observation sequence.
	 */
	public ArrayList<O> observationSequence(int length) {
		if (length <= 0)
			throw new IllegalArgumentException("Positive length required");

		ArrayList<O> sequence = new ArrayList<O>();
		while (length-- > 0)
			sequence.add(observation());
		newSequence();

		return sequence;
	}

	/**
	 * Finds a new state according to the initial (pi) probabilities of each
	 * state.
	 */
	public void newSequence() {
		double rand = r.nextDouble();
		double current = 0.;

		for (int i = 0; i < hmm.nbStates() - 1; i++) {
			current += hmm.getPi(i);

			if (current > rand) {
				stateNb = i;
				return;
			}
		}

		stateNb = hmm.nbStates() - 1;
	}

	/**
	 * Returns the state number of the current state.
	 * 
	 * @return A state number.
	 */
	public int stateNb() {
		return stateNb;
	}
}
